<?php
/**
 * Magic object class to handle the data and build a standard object that inherits to all subclasses
 * 
 * @author Michael Ruf
 */
class ZeusLib_Object
{

	/**
	 * Object attributes
	 *
	 * @var array
	 */
	protected $_data = array();

	/**
	 * Data changed flag (true after setData|unsetData call)
	 *
	 * @var bool
	 */
	protected $_hasDataChanged = false;

	/**
	 * Object delete flag
	 *
	 * @var boolean
	 */
	protected $_isDeleted = false;

	/**
	 * Underscore cache to reduce using preg_replace
	 * 
	 * @var array
	 */
	private static $_underscoreCache = array();


	/**
	 * Constructor
	 * By default is looking for first argument as array and assignes it as object attributes
	 * 
	 * @param [array] $data
	 */
	public function __construct(array $data = array())
	{
		// Check whether data was given
		if (!empty($data))
		{
			// Initialize data
			$this->_data = $data;
		}

		// Call internal constructor
		$this->_construct();
	}

	/**
	 * Internal constructor not depended on params. Can be used for object initialization
	 */
	protected function _construct()
	{
	}

	/**
	 * Add data to the object.
	 *
	 * Overwrites previous data in the object with the same index.
	 *
	 * @param array $data
	 * @return Zeus_Object
	 */
	public function addData(array $data)
	{
		// Iterate the given data array
		foreach($data as $key => $value)
		{
			// Set/overwrite existing data
			$this->setData($key, $value);
		}

		// Return instance
		return $this;
	}

	/**
	 * Overwrites data in the object.
	 *
	 * $key can be string or array.
	 * If $key is string, the attribute value will be overwritten by $value.
	 * If $key is an array, it will overwrite all the data in the object.
	 *
	 * @param string|array $key
	 * @param [mixed] $value
	 * @return Zeus_Object
	 */
	public function setData($key, $value = null)
	{
		// Set data changed flag
		$this->_hasDataChanged = true;

		// Check whether $key is an array
		if(is_array($key))
		{
			// Set the given array to the object data
			$this->_data = $key;
		}
		else
		{
			// Add/overwrite existing data
			$this->_data[$key] = $value;
		}

		// Return instance
		return $this;
	}
	
	/**
	 * Gets data from the object.
	 * 
	 * If $key is not set it returns all data.
	 * If $key is set it returns the data for the given key if exists or false.
	 * 
	 * @param [mixed] $key
	 * @return mixed|boolean|array
	 */
	public function getData($key = null)
	{
		// Check whether no key is given
		if($key === null)
		{
			// Return all data
			return $this->_data;
		}
		
		// Check whether the data for the key not exists
		if(!$this->hasData($key))
		{
			// Return null
			return null;
		}
		
		// Return the data for the key
		return $this->_data[$key];
	}

	/**
	 * Checks whether the object contains data.
	 * 
	 * If $key is not set it returns whether the object has some data
	 * If $key is set it return whether the object contains the data for the index
	 * 
	 * @param [mixed] $key
	 * @return boolean
	 */
	public function hasData($key = null)
	{
		// Check whether no key is given
		if($key === null)
		{
			// Count the data array
			$count = count($this->_data);

			// Return if there is data
			return $count > 0;
		}
		
		// Return whether the data for the given index exists
		return isset($this->_data[$key]);
	}

	/**
	 * Unsets data from the object.
	 * 
	 * If $key is not set it deletes all data
	 * If $key is set it deletes data for the given index
	 *
	 * @param [mixed] $key
	 * @return Zeus_Object
	 */
	public function unsetData($key = null)
	{
		// Set data changed flag
		$this->_hasDataChanged = true;

		// Check whether no key is given
		if($key === null)
		{
			// Reset the data
			$this->_data = array();
		}
		
		// Unset data for the given index
		unset($this->_data[$key]);
		
		// Return instance
		return $this;
	}
	
	/**
	 * Sets the delete flag for the object
	 * 
	 * @param boolean $bool
	 * @return Zeus_Object
	 */
	public function isDeleted($bool = null)
	{
		// Check whether no param is given
		if($bool === null)
		{
			// Return current delete state
			return $this->_isDeleted;
		}
		
		// Set delete state to param
		$this->_isDeleted = (boolean)$bool;
		
		// Return instance
		return $this;
	}
	
	/**
	 * Sets or gets the data changed flag if a param is given or not.
	 * 
	 * @param [boolean] $flag
	 * @return boolean|Zeus_Object
	 */
	public function dataChanged($flag = null)
	{
		// Check whether no flag was given
		if($flag === null)
		{
			// Return current data changed flag
			return $this->_hasDataChanged;
		}
		
		// Set data changed flag
		$this->_hasDataChanged = (bool)$flag;
		
		// Return instance
		return $this;
	}
	
	/**
	 * Set, Get, Unset and Has attribute wrapper
	 *
	 * @param   string $method
	 * @param   array $args
	 * @return  mixed
	 */
	public function __call($method, $args)
	{
		switch (substr($method, 0, 3)) {
			case 'get' :
				$key = $this->_underscore(substr($method,3));
				return $this->getData($key, isset($args[0]) ? $args[0] : null);
	
			case 'set' :
				$key = $this->_underscore(substr($method,3));
				return $this->setData($key, isset($args[0]) ? $args[0] : null);
	
			case 'uns' :
				$key = $this->_underscore(substr($method,3));
				return $this->unsetData($key);
	
			case 'has' :
				$key = $this->_underscore(substr($method,3));
				return $this->hasData($key);
		}
		throw new Zeus_Exception("Invalid method ".get_class($this)."::".$method."(".print_r($args,1).")");
	}
	
	
	/**
	 * Converts field names for setters and getters
	 *
	 * $this->setMyField($value) === $this->setData('my_field', $value)
	 * Uses cache to eliminate unneccessary preg_replace
	 *
	 * @param string $name
	 * @return string
	 */
	protected function _underscore($name)
	{
		if (isset(self::$_underscoreCache[$name])) {
			return self::$_underscoreCache[$name];
		}
		$result = strtolower(preg_replace('/(.)([A-Z])/', "$1_$2", $name));
		self::$_underscoreCache[$name] = $result;
		return $result;
	}
	
	protected function _camelize($name)
	{
		// TODO ?
	}

}